package com.exam.demo.service.impl;

import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.SerializationUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.exam.demo.entity.Paper;
import com.exam.demo.entity.Question;
import com.exam.demo.mapper.QuestionMapper;
import com.exam.demo.service.QuestionService;
import com.exam.demo.util.NameMap;
import com.exam.demo.util.UtilTest;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 功能描述
 *
 * @author yxj30
 * @date 2022/03/16  19:29
 */

@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements QuestionService {

    @Resource
    QuestionMapper questionMapper;

    @Override
    public HttpServletResponse export(HttpServletResponse response) throws IOException {
        // 从数据库查询出所有的数据
        List<Question> list = list();//questionService.list
        System.out.println(list);
        // 通过工具类创建writer 写出到磁盘路径
//        ExcelWriter writer = ExcelUtil.getWriter(filesUploadPath + "/用户信息.xlsx");
        // 在内存操作，写出到浏览器
        ExcelWriter writer = ExcelUtil.getWriter(true);

        //重点： 自定义标题别名
        Map<String, String> nameMap = NameMap.getQuestionNameMap();
        Set<String> keySet = nameMap.keySet();
        Iterator<String> iterator = keySet.iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            writer.addHeaderAlias(key, nameMap.get(key));
        }

        // 一次性写出list内的对象到excel，使用默认样式，强制输出标题
        writer.write(list, true);

        // 设置浏览器响应的格式
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        String fileName = URLEncoder.encode("题目信息表", "UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");

        ServletOutputStream out = response.getOutputStream();
        writer.flush(out, true);
        out.close();
        writer.close();
        return response;
    }

    /**
     * 导入数据库
     *
     * @param file
     * @return
     */
    @Override
    public boolean imp(MultipartFile file) throws IOException {
        InputStream inputStream = file.getInputStream();
        ExcelReader reader = ExcelUtil.getReader(inputStream);
        //1.读取xlsx数据
        List<Map<String, Object>> readAll = reader.readAll();
        //2.替换表头
        Map<String, String> nameMap = NameMap.getQuestionNameMap();
        List<Question> questions = new LinkedList<>();//存储替换后的question
        for (Map questionMap :
                readAll) {
            Iterator<Map.Entry<String, Object>> iterator = questionMap.entrySet().iterator();
            Map<String, Object> newQuestionMap = new LinkedHashMap<>();
            while (iterator.hasNext()) {
                Map.Entry<String, Object> entry = iterator.next();
                String key = NameMap.getKey(nameMap, entry.getKey());
                if (key == null) {
                    return false;
                }
                if (key == "questionId" || key == "question_id") {//主键为null
                    newQuestionMap.put(key, null);
                } else {
                    newQuestionMap.put(key, entry.getValue());
                }
            }
            System.out.println(newQuestionMap);
            Question question = JSON.parseObject(JSON.toJSONString(newQuestionMap), Question.class);
            questions.add(question);
        }
        System.out.println(questions);
        //3.批量插入数据
        boolean b = saveBatch(questions);
        return b;
    }

    @Override
    public Page<Question> findPage(Page<Question> questionPage, String questionTitle, Integer questionType, String courseId,String teacherId) {
        return questionMapper.findPage(questionPage, questionTitle, questionType, courseId,teacherId);
    }

    /**
     * @param courseId      课程号
     * @param i             题目类型
     * @param questionCount 获取数量
     * @return
     */
    @Override
    public List<Question> listByCourseIdAndQuestionType(String courseId, int i, Integer questionCount, Integer difficult) {
        QueryWrapper<Question> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("course_id", courseId);
        queryWrapper.eq("question_type", i);
        queryWrapper.last("ORDER BY RAND() LIMIT " + questionCount.toString());
        return questionMapper.selectList(queryWrapper);
    }

    //获取一道题目
    public Question getQuestionByCourseIdAndQuestionType(String courseId, int i) {
        QueryWrapper<Question> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("course_id", courseId);
        queryWrapper.eq("question_type", i);
        queryWrapper.last("ORDER BY RAND() LIMIT 1");
        return questionMapper.selectOne(queryWrapper);
    }

    //随机组卷
    @Override
    public ArrayList<Question> random(Paper paper) {
        //courseId 和题目数量 -> 题目
        //初始化种群
        ArrayList<ArrayList<Question>> population = initPopulation(paper);
        UtilTest.print(population);
        ArrayList<Double> maxList = new ArrayList<>();
        ArrayList<ArrayList<Question>> maxQuestionList = new ArrayList<>();
        for (int i = 0; i < iterations; i++) {
            //判断是否符合要求
            //计算种群适应度,越大越好
            ArrayList<Double> fitness = fitness(population, paper);
            Double max = Collections.max(fitness);
            int maxIndex = fitness.indexOf(max);
            ArrayList<Question> questions = population.get(maxIndex);
            System.out.println("最优个体" + i);
            UtilTest.printIndividual(questions);
            maxList.add(max);
            ArrayList<Question> clone = SerializationUtils.clone(questions);//最优个体
            maxQuestionList.add(clone);
            if (max > 1) {
                System.out.println("满足要求，提前退出");
            }
            System.out.println("第" + i + "迭代开始");
            //选择算法
            ArrayList<ArrayList<Question>> selectPopulation = select(population, paper);
            //交配算法
            ArrayList<ArrayList<Question>> matePopulation = mate(selectPopulation);
            //变异算法
            ArrayList<ArrayList<Question>> mutatePopulation = mutate(matePopulation, paper);
            population = mutatePopulation;
            //精英主义
            if (elitism) {
                population.set(0, clone);
            }
            System.out.println();
        }
        System.out.println("迭代结束");
        //查结果
        System.out.println("最终结果：");
        Double max = Collections.max(maxList);
        System.out.println("最大适应度 = " + max);
        System.out.println("误差 = " + 1 / max);
        int maxIndex = maxList.indexOf(max);
        ArrayList<Question> questions = maxQuestionList.get(maxIndex);
        return questions;
    }

    /**
     * 交配概率
     */
    private static final double mateRate = 0.8;
    /**
     * 变异概率
     */
    private static final double mutationRate = 0.085;
    /**
     * 精英主义
     */
    private static final boolean elitism = true;
    /**
     * 淘汰数组大小
     */
    private static final int tournamentSize = 5;
    /**
     * 种群大小
     */
    private static final int populationSize = 40;

    private static final int iterations = 20;

    private static final double coverageWeight = 0.2; //知识点权重
    private static final double difficultWeight = 0.8;//难度权重



    //初始化个体
    public ArrayList<Question> init(Paper paper) {
        String courseId = paper.getCourseId();
        Integer radioQuestionCount = paper.getRadioQuestionCount();
        Integer checkboxQuestionCount = paper.getCheckboxQuestionCount();
        Integer judgeQuestionCount = paper.getJudgeQuestionCount();
        Integer difficult = paper.getDifficult();
        ArrayList<Question> list = new ArrayList<>();

        if (radioQuestionCount != null && radioQuestionCount != 0) {
            list.addAll(listByCourseIdAndQuestionType(courseId, 1, radioQuestionCount, difficult));
        }
        if (checkboxQuestionCount != null && checkboxQuestionCount != 0) {
            list.addAll(listByCourseIdAndQuestionType(courseId, 2, checkboxQuestionCount, difficult));
        }
        if (judgeQuestionCount != null && judgeQuestionCount != 0) {
            list.addAll(listByCourseIdAndQuestionType(courseId, 3, judgeQuestionCount, difficult));
        }
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        return list;
    }
    //初始化种群
    public ArrayList<ArrayList<Question>> initPopulation(Paper paper) {
        ArrayList<ArrayList<Question>> population = new ArrayList<>();
        for (int i = 0; i < populationSize; i++) {
            population.add(init(paper));
        }
        return population;
    }
    //选择算法
    public ArrayList<ArrayList<Question>> select(ArrayList<ArrayList<Question>> population, Paper paper) {
        //计算适应度
        ArrayList<Double> fitnessList = fitness(population, paper);//适应度数组
//        UtilTest.printFitness(fitnessList);
        //轮盘选择
        ArrayList<ArrayList<Question>> rws = RWS(population, fitnessList);
        //返回新种群
        return rws;
    }

    //轮盘算法
    public ArrayList<ArrayList<Question>> RWS(ArrayList<ArrayList<Question>> population, ArrayList<Double> fitnessList) {
        //计算累计适应度
        ArrayList<Double> cumList = new ArrayList<>();
        Double cum = 0D;
        Double sum = fitnessList.stream().reduce(Double::sum).orElse(0D);
        for (int i = 0; i < fitnessList.size(); i++) {
            cum += fitnessList.get(i);
            cumList.add(cum / sum);
        }
        //随机轮盘
//        System.out.println("轮盘选中个体： ");
        ArrayList<ArrayList<Question>> newPopulation = new ArrayList<ArrayList<Question>>();
        for (int i = 0; i < population.size(); i++) {
            double random = Math.random();
            for (int j = 0; j < cumList.size(); j++) {
                if (cumList.get(j) > random) {
                    //选中
                    newPopulation.add(population.get(j));//把选中的第j个元素，添加到新种群
                    //打印轮盘选中的个体
//                    System.out.print(j+",");
                    break;
                }
            }
        }
        System.out.println("cumList" + cumList.toString());
        //返回种群
        return newPopulation;
    }

    //计算种群适应度
    public ArrayList<Double> fitness(ArrayList<ArrayList<Question>> population, Paper paper) {
        Integer paperDifficult = paper.getDifficult();//试卷难度
        ArrayList<Double> fitnessList = new ArrayList<>();
        for (int i = 0; i < population.size(); i++) {
            //分别计算个体适应度
            Double sum = 0D;
            ArrayList<Question> individual = population.get(i);
            System.out.println("计算第" + i + "个体适应度");
            for (int j = 0; j < individual.size(); j++) {
                //分别计算每题的适应度
                Integer questionDifficult = individual.get(j).getDifficult();
                sum += Math.abs(paperDifficult - questionDifficult);
                System.out.println(individual.get(j).toString());
                System.out.println("paperDifficult = " + paperDifficult);
                System.out.println("questionDifficult = " + questionDifficult);
                System.out.print("sum:" + sum);
            }
            double d = 1 / (sum + 0.0001D);
            System.out.println("d = " + d);
            //添加知识点覆盖率
            HashSet<String> strings = new HashSet<>();
            for (int j = 0; j < individual.size(); j++) {
                Question question = individual.get(j);
                String knowledge = question.getKnowledge();
                System.out.println("knowledge = " + knowledge);
                if (!StringUtils.isEmpty(knowledge)) {
                    String[] split = knowledge.split(",");
                    for (int k = 0; k < split.length; k++) {
                        strings.add(split[k]);
                    }
                }
            }
            System.out.println("覆盖知识点个数：" + strings.size());
            int coverage = (individual.size() - strings.size());
            fitnessList.add(difficultWeight * 1 / (sum + 0.0001D) + coverageWeight * 1 / (coverage + 0.0001D));
        }
        return fitnessList;
    }

    //交配算法
    public ArrayList<ArrayList<Question>> mate(ArrayList<ArrayList<Question>> population) {
        //是否需要打乱
//        Collections.shuffle(population);
        int individualSize = population.get(0).size();
        //arraylist转数组
        ArrayList<ArrayList<Question>> matePopulation = new ArrayList<>();
        int i = 0;
        while (i < population.size()) {
            //单数情况
            if ((i + 1) == population.size()) {
                matePopulation.add(population.get(i));
            }
            if (Math.random() < mateRate) {
                matePopulation.add(population.get(i));
                matePopulation.add(population.get(i + 1));
            } else {
                //两两完成交配
                //复制个体
                ArrayList<Question> questionList1 = new ArrayList<>();
                ArrayList<Question> questionList2 = new ArrayList<>();
                int random = (int) (Math.random() * (individualSize));
                if (random == 0) {
                    continue;//跳过本轮
                }
//                System.out.println("random = " + random);
//                System.out.println("交配前");
//                System.out.println("list1：");
//                UtilTest.printIndividual(population.get(i));
//                System.out.println("list2：");
//                UtilTest.printIndividual(population.get(i + 1));

                questionList1.addAll(population.get(i).subList(0, random));
                questionList1.addAll(population.get(i + 1).subList(random, individualSize));
                questionList2.addAll(population.get(i + 1).subList(0, random));
                questionList2.addAll(population.get(i).subList(random, individualSize));
                //判断是否交配是否符合题目不同要求，否则取消交配
                Set<Integer> collect1 = questionList1.stream().map(Question::getQuestionId).collect(Collectors.toSet());
                Set<Integer> collect2 = questionList2.stream().map(Question::getQuestionId).collect(Collectors.toSet());
                if (collect1.size() == 5 && collect2.size() == 5) {
                    matePopulation.add(questionList1);
                    matePopulation.add(questionList2);
                    System.out.println("交配成功");
                } else {
                    //取消交配
                    matePopulation.add(population.get(i));
                    matePopulation.add(population.get(i + 1));
                    System.out.println("交配失败");
                }

//                System.out.println("交配后");
//                System.out.println("list1：");
//                UtilTest.printIndividual(population.get(i));
//                System.out.println("list2：");
//                UtilTest.printIndividual(population.get(i + 1));
//                System.out.println();
            }
            i = i + 2;

        }
        //返回结果
        return matePopulation;
    }

    //变异算法
    public ArrayList<ArrayList<Question>> mutate(ArrayList<ArrayList<Question>> population, Paper paper) {
        //改变个体的某个基因
        int individualSize = population.get(0).size();
        for (int i = 0; i < population.size(); i++) {
            //随机一个数，判断是否变异
            double random = Math.random();
            if (random < mutationRate) {
//                System.out.println("变异前：");
//                UtilTest.printIndividual(population.get(i));
                //随机变异位置
                int index = (int) (Math.random() * individualSize);
//                System.out.println("变异位置：" + index);
                //根据index，判断题目类型
                //从题库搜索题目
                Question question = findQuestionTypeByIndex(index, paper);
                if (question != null) {
                    //是否符合题目要求
                    ArrayList<Question> clone = SerializationUtils.clone(population.get(i));
                    clone.set(index, question);
                    Set<Integer> collect = clone.stream().map(Question::getQuestionId).collect(Collectors.toSet());
                    System.out.println("collect.size() = " + collect.size());
                    if (collect.size() == population.get(i).size()) {
                        population.get(i).set(index, question);//第i个个体，修改index位置的问题question
                        System.out.println("变异成功");
                    } else {
                        System.out.println("变异失败");
                    }
                }
//                System.out.println("变异后：");
//                UtilTest.printIndividual(population.get(i));
            }
        }
        return population;
    }

    //根据index，判断题目类型
    public Question findQuestionTypeByIndex(int index, Paper paper) {
        Integer radioQuestionCount = paper.getRadioQuestionCount();
        Integer checkboxQuestionCount = paper.getCheckboxQuestionCount();
        Integer judgeQuestionCount = paper.getJudgeQuestionCount();
        int[] ints = new int[3];
        ints[0] = radioQuestionCount;
        ints[1] = radioQuestionCount + checkboxQuestionCount;
        ints[2] = radioQuestionCount + checkboxQuestionCount + judgeQuestionCount;
        for (int i = 0; i < ints.length; i++) {
            if (index < ints[i]) {
                //找到题目类型
                Question question = getQuestionByCourseIdAndQuestionType(paper.getCourseId(), i + 1);
                return question;
            }
        }
        System.out.println("查找不到题目");

        return null;
    }


}
